#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <cassert>
#include <fstream>
#include <ctemplate/template.h>
#include <jsoncpp/json/json.h>

#include "model.hpp"
#include "view.hpp"
#include "../common/log.hpp"
#include "../common/common.hpp"
#include "../common/httplib.h"

namespace wjh_control
{
    const std::string machine_list = "./MachineList.conf";

    using namespace wjh_model;
    using namespace wjh_view;
    using namespace wjh_util;
    using namespace httplib;

    struct Machine
    {
        void DecreaseLoad()
        {
            if (_mtx)
            {
                _mtx->lock();
                _load--;
                _mtx->unlock();
            }
        }

        void IncreaseLoad()
        {
            if (_mtx)
            {
                _mtx->lock();
                _load++;
                _mtx->unlock();
            }
        }

        std::string _ip = "";            // 机器对应ip
        uint16_t _port = 0;             // 机器对应port
        int _load = 0;              // 机器当前的负载量
        std::mutex *_mtx = nullptr; // 锁禁止拷贝, 需要用指针
    };

    class LoadBalance
    {
    public:
        LoadBalance()
        {
            assert(LoadMachine(machine_list));
        }

        // 选择负载最低的机器 id:输出型参数 m:输出型参数
        bool SmartSelect(int & id, Machine *& m)
        {
            _mtx.lock();
            if (_online.size() == 0)
            {
                logMessage(FATAL, "%s", "后台所有参与编译的机器都处于离线, 请及时处理");
                _mtx.unlock();
                return false;
            }

            int minLoad = _ManageMachine[_online[0]]._load;
            id = _online[0];
            m = &_ManageMachine[_online[0]];
            for (int i = 1; i < _online.size(); i++)
            {
                int curLoad = _ManageMachine[_online[i]]._load;
                if (minLoad > curLoad)
                {
                    minLoad = curLoad;
                    id = _online[i];
                    m = &_ManageMachine[_online[i]];
                }
            }

            _mtx.unlock();
            return true;
        }

        bool OnlineMachine(int id)
        {
            return true;
        }

        bool OfflineMachine(int id)
        {
            _mtx.lock();
            for(auto it = _online.begin(); it != _online.end(); it++)
            {
                if(*it == id)
                {
                    _online.erase(it);
                    _offline.push_back(id);
                    _mtx.unlock();
                    return true;
                }
            }
            _mtx.unlock();
            return false;
        }

    private:
        bool LoadMachine(const std::string &machine_list)
        {
            std::ifstream is(machine_list);
            if (!is.is_open())
            {
                logMessage(FATAL, "%s%d%s", __FILE__, __LINE__, "读取编译机器的配置文件失败");
                return false;
            }

            std::string line;
            while (getline(is, line))
            {
                std::vector<std::string> target;
                StringUtil::SplitStr(line, target, ":");
                if (target.size() != 2)
                {
                    logMessage(ERROR, "%s", "机器配置文件格式不对");
                    continue;
                }
                Machine m;
                m._ip = target[0];
                m._port = atoi(target[1].c_str());
                m._mtx = new std::mutex();
                _online.push_back(_ManageMachine.size());
                _ManageMachine.push_back(m);
            }

            is.close();
            return true;
        }

    private:
        std::vector<Machine> _ManageMachine; // 记录所有执行编译服务的机器
        std::vector<int> _online;            // 记录在线的机器
        std::vector<int> _offline;           // 记录离线的机器
        std::mutex _mtx;                     // 保护loadbalance在被多执行流访问时修改_online或者_offline
    };

    class Control
    {
    public:
        // html 输出型参数
        bool GetAllQuestions(std::string &html)
        {
            // 1.向model获取全部题目
            // 2.将数据交给view构建一个网页
            std::vector<struct Question> ret;
            if (_model.GetAllQuestions(ret))
            {
                _view.AllExpandHtml(ret, html);
                return true;
            }
            else
            {
                html = "加载题库失败";
                return false;
            }
        }

        // 获取指定题目 : html 输出型参数
        bool GetQuestion(const std::string &number, std::string &html)
        {
            // 1.向model获取指定编号的题目
            // 2.将数据交给view构建一个网页
            Question q;
            if (_model.GetOneQuestion(number, q))
            {
                _view.OneExpandHtml(q, html);
            }
            else
            {
                html = "加载指定题目：" + number + "失败";
                return false;
            }
            return true;
        }

        bool Judge(const std::string &number, const std::string &in_json, std::string &out_json)
        {
            // 1.反序列化获取到用户所提交的代码
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string SubmitCode = in_value["code"].asString(); // 用户提交的代码
            // 2.与测试用例代码结合并序列化
            Question q;
            Json::Value out_value;
            Json::StyledWriter writer;
            std::string compile;
            if (_model.GetOneQuestion(number, q))
            {
                out_value["code"] = SubmitCode + q.tail;
                out_value["input"] = in_value["input"].asString();
                out_value["cpuLimit"] = q.cpuLimit;
                out_value["memLimit"] = q.memLimit;
                compile = writer.write(out_value);
            }
            else return false;

            while (true)
            {
                int id = 0;
                Machine *m = nullptr;
                if (_balance.SmartSelect(id, m))
                {
                    // 3.选择负载最低的机器，并向他发送http请求
                    m->IncreaseLoad();
                    Client cli(m->_ip, m->_port);
                    logMessage(NORMAL, "%s:%d:%s:%d%s", "load machine", id, m->_ip.c_str(), m->_port, "success");
                    if (auto it = cli.Post("/Start", compile, "application/json; charset=utf-8"))
                    {
                        // 4.接受结果，返回结果
                        if (it->status == 200)
                        {
                            out_json = it->body;
                            m->DecreaseLoad();
                            return true;
                        }
                            
                        m->DecreaseLoad();
                    }
                    else
                    {
                        // 请求失败
                        logMessage(WARNING, "%s%d %s%d", __FILE__, __LINE__, "有机器可能离线, 离线主机id: ", id);
                        m->DecreaseLoad();
                        _balance.OfflineMachine(id);
                    }
                }
                else
                {
                    break;
                }
            }
            return false;
        }

    private:
        Model _model;
        View _view;
        LoadBalance _balance;
    };
}